package cn.gx.kevin.common.utils;

import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.pool.DruidDataSource;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import cn.gx.kevin.common.bootenv.SpringContextUtil;
import cn.gx.kevin.common.thread.DaoDBKeyHolder;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * @author kevin.huang
 * @version v1.0.0
 * @Description: 实现动态多数据源支持
 * @date 2015年3月1日 下午2:56:00
 */
public class DynamicDaoSupport extends SqlSessionDaoSupport {

    final static Logger logger = LoggerFactory.getLogger(DynamicDaoSupport.class);

    static DefaultListableBeanFactory acf;
    static BeanDefinitionRegistry beanDefinitionRegistry;

    static BeanDefinitionBuilder sessionFactorybdb;
    static ApplicationContext context;

    @Resource(name = "defaultSessionFactory")
    protected SqlSessionFactory defaultFactory;

    /**
     * 切换到指定数据库
     ***/
    protected void setDbKey(String key) {
        DaoDBKeyHolder.setKey(key);
    }

    protected String getDbKey() {
        return DaoDBKeyHolder.getKey();
    }

    /**
     * 恢复到默认数据库
     ***/
    protected void clearDbKey() {
        DaoDBKeyHolder.clear();
    }

    @Override
    protected void checkDaoConfig() {

    }

    /*****
     * 获取数据库类型标识:mysql、oracle、db2、sqlserver、h2
     * ******/
    public String getDbType() {
        SqlSession session = getSqlSession();
        DruidDataSource ds = (DruidDataSource) session.getConfiguration().getEnvironment().getDataSource();
        String driverClassName = ds.getDriverClassName();
        String dbType = getDyFlag(driverClassName);
        return dbType;
    }


    private static String getDyFlag(String driverClassName) {
        String dbType = "mysql";
        if (driverClassName.contains("com.mysql")) {
            dbType = "mysql";
        } else if (driverClassName.contains("oracle.jdbc")) {
            dbType = "oracle";
        } else if (driverClassName.contains("com.ibm")) {
            dbType = "db2";
        } else if (driverClassName.contains("sqlserver.jdbc")) {
            dbType = "sqlserver";
        } else if (driverClassName.contains("jdbc:h2")) {
            dbType = "h2";
        }
        return dbType;
    }

    /***
     * 重写根据线程DBkey实现动态数据源
     ***/
    @Override
    public SqlSession getSqlSession() {
        String key = DaoDBKeyHolder.getKey();
        SqlSession session = getSession(key);
        if (session == null) {
            return this.defaultFactory.openSession(true);
        }
        return session;
    }

    /****
     * 获取session的公开接口
     * *****/
    public SqlSession getSession(String key) {
        if (StringUtils.isEmpty(key)) {
            return this.defaultFactory.openSession(true);
        }
        setSpringEvn();
        SqlSessionFactory facotry = null;
        try {
            Object tmp = context.getBean("ssf_" + key);
            facotry = (SqlSessionFactory) tmp;
        } catch (Exception e) {
            logger.error("", e);
            return null;
        }
        return facotry.openSession(true);
    }


    private static void setSpringEvn() {
        if (context == null) {
            context = SpringContextUtil.getApplicationContext();
            acf = (DefaultListableBeanFactory) context.getAutowireCapableBeanFactory();
            beanDefinitionRegistry = (DefaultListableBeanFactory) context.getAutowireCapableBeanFactory();
        }
    }

    /**
     * @param dbList
     * @param onCreated
     * @Description: 初始化多数据源
     * @Description: TODO
     */
    public static void initDataSource(List<DataSourceInfo> dbList, IOnDataSourceCreated onCreated) {
        // dbList = null;
        if (dbList == null || dbList.size() == 0) {
            return;
        }
        setSpringEvn();
        final List<DataSourceInfo> dbs = dbList;
        final IOnDataSourceCreated onCreatedEv = onCreated;
        try {
            // 采用线程去创建，防止影响系统启动
            new Thread(new Runnable() {
                List<DataSourceInfo> fail = new ArrayList<DataSourceInfo>();

                @Override
                public void run() {
                    for (DataSourceInfo db : dbs) {
                        try {
                            int sourceType = db.getType();
                            if (sourceType == 1) {
                                String jndiName = null;
                                Object jndi = db.getProperty("jndiName");
                                if (jndi != null) {
                                    jndiName = String.valueOf(jndi);
                                }
                                if (StringUtils.isNotEmpty(jndiName)) {
                                    putJNDIDataSource(jndiName);
                                }
                            } else {
                                createDataSource(db);
                            }
                            if (logger.isDebugEnabled()) {
                                logger.debug("The dataSource [{}] init success!", db.toString());
                            }
                        } catch (Exception ex) {
                            fail.add(db);
                            logger.error("{}", ex);
                        }
                    }
                    if (onCreatedEv != null) {
                        onCreatedEv.onCreated(fail);
                    }

                }
            }, "web-init-datasource-thread").start();
        } catch (Exception e) {
            logger.error("{}", e);
        }
    }

    public static boolean createDataSource(DataSourceInfo dsInfo) throws Exception {
        setSpringEvn();
        removeDataSource(dsInfo.getId());
        Map<String, Object> props = dsInfo.getProperties();
        String dataSourceClass = String.valueOf(props.get("dataSourceClass"));
        DataSource dataSource = createDruidDataSource(dsInfo);
        if (dataSource != null) {
            boolean isXa = dataSourceClass.toLowerCase().indexOf("xa") > 0;
            if (isXa) { // xa跨库事务
                dataSource = createAtomikosDataSource(dsInfo, (DruidDataSource) dataSource);
            }
            Object dialectObjct = props.get("dialect");
            String dialect;
            if (dialectObjct == null) {
                dialect = getDyFlag(dsInfo.getDriverClass());
            } else {
                dialect = String.valueOf(dialectObjct);
            }
            createSesseionFactory(dsInfo.getId(), dataSource, dsInfo.getMybatisCfgClassPath(), dsInfo.getMapperCfgClassPath(), dialect);
            /*******
             * 创建事务管理器,本地事务是无效，出发是xa事务！
             * ********/
           /* if(!isXa){
                Object transactionManagerClass = props.get("localTransactionManager");
                if(transactionManagerClass != null){
                    *//****创建事务管理器，关联datasource****//*
                    String beanId = "mrg_" + dsInfo.getId();
                    BeanDefinitionBuilder transactionMgrFactorybdb = BeanDefinitionBuilder.genericBeanDefinition(String.valueOf(transactionManagerClass));
                    transactionMgrFactorybdb.addPropertyValue("dataSource", dataSource);
                    AbstractBeanDefinition def = transactionMgrFactorybdb.getBeanDefinition();
                    acf.registerBeanDefinition(beanId, def);
                    DataSourceTransactionManager mrgbean = (DataSourceTransactionManager) context.getBean(beanId);
                    //SpringManagedTransaction transaction

                    *//*******事务 advice 拦截创建********//*
                    beanId = "advice_"+dsInfo.getId();
                    String adviceBeanName = beanId;
                    BeanDefinitionBuilder interceptordbd = BeanDefinitionBuilder.genericBeanDefinition(TransactionInterceptor.class);
                    def = interceptordbd.getBeanDefinition();
                    interceptordbd.addPropertyValue("transactionManager",mrgbean);
                    Properties itrProps = new Properties();
                    String attr = "PROPAGATION_SUPPORTS,ISOLATION_DEFAULT,-java.lang.Exception";
                    itrProps.put("get*",attr);
                    itrProps.put("load*",attr);
                    itrProps.put("select*",attr);
                    itrProps.put("find*",attr);
                    itrProps.put("count*",attr);
                    itrProps.put("query*",attr);
                    itrProps.put("list*",attr);
                    itrProps.put("read*",attr);
                    itrProps.put("unique*",attr);
                    itrProps.put("*","PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-java.lang.Exception");
                    interceptordbd.addPropertyValue("transactionAttributes",itrProps);
                    acf.registerBeanDefinition(beanId, def);
                    TransactionInterceptor adviceBean = (TransactionInterceptor) context.getBean(beanId);

                    *//*** pointcut spring中必须配置 serviceMethods 的pointcut***//*
                    beanId = "pointcut_"+dsInfo.getId();
                    AspectJExpressionPointcut pointcutCfgBean =(AspectJExpressionPointcut) context.getBean("serviceMethods");
                    String exprs = pointcutCfgBean.getExpression();
                    BeanDefinitionBuilder aopdbd = BeanDefinitionBuilder.genericBeanDefinition(AspectJExpressionPointcut.class);
                    aopdbd.addPropertyValue("expression",exprs);
                    def = aopdbd.getBeanDefinition();
                    acf.registerBeanDefinition(beanId, def);
                    AspectJExpressionPointcut pointcutBean = (AspectJExpressionPointcut) context.getBean(beanId);


                    //DefaultBeanFactoryPointcutAdvisor advisorBean = (DefaultBeanFactoryPointcutAdvisor)context.getBean("aopadvisor");
                    *//********  创建 Advisor *********//*
                    beanId = "advisor_"+dsInfo.getId();
                    BeanDefinitionBuilder addbd = BeanDefinitionBuilder.genericBeanDefinition(DefaultBeanFactoryPointcutAdvisor.class);
                    addbd.addPropertyValue("adviceBeanName",adviceBeanName);
                    addbd.addPropertyValue("pointcut",pointcutBean);

                    def = addbd.getBeanDefinition();
                    acf.registerBeanDefinition(beanId, def);
                    DefaultBeanFactoryPointcutAdvisor adBean = (DefaultBeanFactoryPointcutAdvisor) context.getBean(beanId);

                    logger.debug("创建事务管理器完成="+beanId);
                }
            }*/
        } else {
            removeDataSource(dsInfo.getId());
            return false;
        }
        return true;
    }

    private static void addDataSourceFilter(String banName, List<Filter> filterArrays) {
        try {
            Object filterBean = context.getBean(banName);
            if (filterBean != null) {
                Filter logFilter = (Filter) filterBean;
                filterArrays.add(logFilter);
            }
        } catch (Exception e) {
            if (logger.isDebugEnabled()) {
                logger.debug("", e);
            }
        }
    }

    private static DruidDataSource createDruidDataSource(DataSourceInfo dsInfo) {

        String beanId = "dy_db_source_" + dsInfo.getId();
        Map<String, Object> props = dsInfo.getProperties();
        int initialSize = Integer.valueOf(String.valueOf(props.get("initialSize")));
        int minIdle = Integer.valueOf(String.valueOf(props.get("minIdle")));
        int maxActive = Integer.valueOf(String.valueOf(props.get("maxActive")));
        int maxWait = Integer.valueOf(String.valueOf(props.get("maxWait")));
        int timeBetweenEvictionRunsMillis = Integer.valueOf(String.valueOf(props.get("timeBetweenEvictionRunsMillis")));
        int minEvictableIdleTimeMillis = Integer.valueOf(String.valueOf(props.get("minEvictableIdleTimeMillis")));
        boolean testWhileIdle = Boolean.parseBoolean(String.valueOf(props.get("testWhileIdle")));
        boolean testOnBorrow = Boolean.parseBoolean(String.valueOf(props.get("testOnBorrow")));
        boolean testOnReturn = Boolean.parseBoolean(String.valueOf(props.get("testOnReturn")));
        String filters = props.get("filters") == null ? "" : String.valueOf(props.get("filters"));
        String dataSourceClass = String.valueOf(props.get("dataSourceClass"));
        String validationQuery = props.get("validationQuery") != null ? String.valueOf(props.get("validationQuery")) : "";
        if(dsInfo.driverClass.contains("oracle")){
            validationQuery = "SELECT 1 FROM DUAL";
        }else if(dsInfo.driverClass.contains("mysql")){
            validationQuery = "SELECT 1 ";
        }
        boolean removeAbandoned = Boolean.parseBoolean(String.valueOf(props.get("removeAbandoned")));
        int removeAbandonedTimeout = Integer.valueOf(String.valueOf(props.get("removeAbandonedTimeout")));

        BeanDefinitionBuilder druidDataSourceFactorybdb = BeanDefinitionBuilder.genericBeanDefinition(dataSourceClass);
        if (StringUtils.isNotEmpty(validationQuery)) {
            druidDataSourceFactorybdb.addPropertyValue("validationQuery", validationQuery);
        }
        druidDataSourceFactorybdb.addPropertyValue("initialSize", initialSize);
        druidDataSourceFactorybdb.addPropertyValue("minIdle", minIdle);
        druidDataSourceFactorybdb.addPropertyValue("maxActive", maxActive);
        druidDataSourceFactorybdb.addPropertyValue("maxWait", maxWait);
        druidDataSourceFactorybdb.addPropertyValue("timeBetweenEvictionRunsMillis", timeBetweenEvictionRunsMillis);
        druidDataSourceFactorybdb.addPropertyValue("minEvictableIdleTimeMillis", minEvictableIdleTimeMillis);
        druidDataSourceFactorybdb.addPropertyValue("testWhileIdle", testWhileIdle);
        druidDataSourceFactorybdb.addPropertyValue("testOnBorrow", testOnBorrow);
        druidDataSourceFactorybdb.addPropertyValue("testOnReturn", testOnReturn);
        if (StringUtils.isNotEmpty(filters)) {
            druidDataSourceFactorybdb.addPropertyValue("filters", filters);
        }
        druidDataSourceFactorybdb.addPropertyValue("removeAbandoned", removeAbandoned);
        druidDataSourceFactorybdb.addPropertyValue("removeAbandonedTimeout", removeAbandonedTimeout);
        List<Filter> filterArrays = new ArrayList<Filter>();
        addDataSourceFilter("dblogFilter", filterArrays);
        addDataSourceFilter("druid_stat_filter", filterArrays);
        addDataSourceFilter("druid_wall_filter", filterArrays);
        addDataSourceFilter("druid_log_filter", filterArrays);

        if (filterArrays.size() > 0) {
            druidDataSourceFactorybdb.addPropertyValue("proxyFilters", filterArrays);
        }

        AbstractBeanDefinition def = druidDataSourceFactorybdb.getBeanDefinition();
        String driverClass = dsInfo.getDriverClass();
        String url = dsInfo.getUrl();
        String userName = dsInfo.getUsername();
        String password = dsInfo.getPassword();
        druidDataSourceFactorybdb.addPropertyValue("driverClassName", driverClass);
        druidDataSourceFactorybdb.addPropertyValue("url", url);
        druidDataSourceFactorybdb.addPropertyValue("username", userName);
        druidDataSourceFactorybdb.addPropertyValue("password", password);

        acf.registerBeanDefinition(beanId, def);

        DruidDataSource bean = (DruidDataSource) context.getBean(beanId);

        try {
            Connection conn = bean.getConnection();
            if (!conn.isClosed()) {
                conn.close();
            }
        } catch (SQLException e) {
            logger.error("", e);
            return null;
        }
        return (bean);
    }

    private static AtomikosDataSourceBean createAtomikosDataSource(DataSourceInfo dsInfo, DruidDataSource druidSource) {

        String beanId = "atomikos_ds_" + dsInfo.getId();

        int maintenanceInterval = Integer.valueOf(String.valueOf(dsInfo.getxaProps("maintenanceInterval")));
        int poolSize = Integer.valueOf(String.valueOf(dsInfo.getxaProps("poolSize")));
        String testQuery = String.valueOf(dsInfo.getxaProps("testQuery"));

        BeanDefinitionBuilder atomikosSourceFactorybdb = BeanDefinitionBuilder.genericBeanDefinition("com.atomikos.jdbc.AtomikosDataSourceBean");
        AbstractBeanDefinition def = atomikosSourceFactorybdb.getBeanDefinition();
        atomikosSourceFactorybdb.addPropertyValue("uniqueResourceName", beanId);
        atomikosSourceFactorybdb.addPropertyValue("xaDataSource", druidSource);
        atomikosSourceFactorybdb.addPropertyValue("poolSize", poolSize);
        atomikosSourceFactorybdb.addPropertyValue("testQuery", testQuery);
        atomikosSourceFactorybdb.addPropertyValue("maintenanceInterval", maintenanceInterval);

        acf.registerBeanDefinition(beanId, def);
        AtomikosDataSourceBean bean = (AtomikosDataSourceBean) context.getBean(beanId);
        return bean;
    }

    /**
     * @return
     * @throws Exception
     * @Description: 创建LocalSessionFactoryBean
     */
    private static SqlSessionFactory createSesseionFactory(String beanId, DataSource dataSource,
                                                           String mybatisCfgClassPath, String mapperCfgClassPath, String dialect) throws Exception {

        if (sessionFactorybdb == null) {
            sessionFactorybdb = BeanDefinitionBuilder.genericBeanDefinition("org.mybatis.spring.SqlSessionFactoryBean");
        }

        beanId = "ssf_" + beanId;
        AbstractBeanDefinition def = sessionFactorybdb.getBeanDefinition();

        PathMatchingResourcePatternResolver resoler = new PathMatchingResourcePatternResolver();
        org.springframework.core.io.Resource[] resources = (org.springframework.core.io.Resource[]) resoler
                .getResources(mybatisCfgClassPath);
        sessionFactorybdb.addPropertyValue("configLocation", resources);

        org.springframework.core.io.Resource[] resources1 = (org.springframework.core.io.Resource[]) resoler
                .getResources(mapperCfgClassPath);
        sessionFactorybdb.addPropertyValue("mapperLocations", resources1);

        /*****分页插件******/
        com.github.pagehelper.PageInterceptor interceptor = new com.github.pagehelper.PageInterceptor();
        /*****触发默认值即可*******/
        Properties prop = new Properties();
        interceptor.setProperties(prop);

        Interceptor[] plugins = new Interceptor[]{interceptor};

        sessionFactorybdb.addPropertyValue("plugins", plugins);

        sessionFactorybdb.addPropertyValue("dataSource", dataSource);
        acf.registerBeanDefinition(beanId, def);
        Object bean = context.getBean(beanId);
        SqlSessionFactory factory = (SqlSessionFactory) bean;
        return factory;
    }

    /**
     * @param key
     * @Description: 移除
     */
    public static boolean removeDataSource(String key) {
        boolean isOk = true;
        String beanName = "dy_db_source_" + key;
        /** 移除DruidDataSource **/
        try {
            if (beanDefinitionRegistry.containsBeanDefinition(beanName)) {
                beanDefinitionRegistry.removeBeanDefinition(beanName);
            }
        } catch (Exception e) {

        }
        /** 移除AtomikosDataSource ***/
        beanName = "atomikos_ds_" + key;
        try {
            if (beanDefinitionRegistry.containsBeanDefinition(beanName)) {
                beanDefinitionRegistry.removeBeanDefinition(beanName);
            }
        } catch (Exception e) {
        }
        /** 移除spring中的SqlSessionFactory **/
        beanName = "ssf_" + key;
        try {
            if (beanDefinitionRegistry.containsBeanDefinition(beanName)) {
                beanDefinitionRegistry.removeBeanDefinition(beanName);
            }
        } catch (Exception e) {

        }
        return isOk;
    }

    /**
     * @return
     * @throws Exception
     * @Description: 测试数据源是否可用
     */
    public static boolean testDataSource(String id) throws Exception {
        boolean isOk = false;
        String beanId = "dy_db_source_" + id;
        Connection conn = null;
        try {
            DataSource dataSource = (DataSource) context.getBean(beanId);
            conn = dataSource.getConnection();
            if (!conn.isClosed()) {
                conn.close();
                conn = null;
            }
            isOk = true;
        } catch (Exception ex) {
            logger.debug("testDataSource {}", ex);
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (Exception ex) {
                }
            }
        }
        return isOk;
    }

    /**
     * @param jndiName
     * @return
     * @Description: 添加JNDI数据源
     */
    private static boolean putJNDIDataSource(String jndiName) {
        return true;
    }

}
